package org.fatewa.engine.endpoint;

import cn.hutool.core.util.TypeUtil;
import com.baomidou.mybatisplus.extension.service.IService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.fatewa.engine.design.base.PrimaryKeyProvider;
import org.fatewa.engine.design.genius.GenericHooks;
import org.fatewa.engine.design.hooks.EndpointHooks;
import org.fatewa.engine.endpoint.hooks.EndpointHookInvoker;
import org.fatewa.engine.endpoint.hooks.impl.BuiltinEndpointHookInvoker;
import org.fatewa.engine.experimental.poi.Intermediary;
import org.fatewa.engine.genius.annotations.Export;
import org.fatewa.engine.genius.annotations.Relation;
import org.fatewa.engine.genius.annotations.RelationConnector;
import org.fatewa.engine.genius.annotations.Relations;
import org.fatewa.engine.genius.base.GenericController;
import org.fatewa.engine.genius.base.GenericRelationController;
import org.fatewa.engine.property.RayquazaProperties;
import org.fatewa.engine.utils.ProxyUtils;
import org.jetbrains.annotations.Nullable;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.io.Serializable;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;

import static org.fatewa.engine.genius.utils.GenericUtils.getRelationMappingInfo;

/**
 * Rayquaza 的入口点，在这里会进行动态路由注册与 Hooks 注入
 * 由于上个版本的实现过于集中，在本次实现中，会进行拆解实现，
 * 并提供一个对外部用户进行扩展的接口，
 * 同时入口点本身也会有 Hook 提供
 * {@link EndpointHooks}
 *
 * @author 4everlynn
 */
@Slf4j
public class RayquazaEndpoint implements ApplicationRunner {
    /**
     * Hook 执行器
     */
    private final EndpointHookInvoker invoker = new BuiltinEndpointHookInvoker();
    /**
     * 接口 Hook 收集
     * Key 为实体类型， Value 为Hook 集合，本次的 Hook 支持注册多个
     */
    private static final Map<Class<?>, Collection<GenericHooks<?, ?>>> HOOKS_MAP = new LinkedHashMap<>();

    /**
     * 存储服务，加速注入过程
     */
    private static final Map<String, IService<?>> SERVICE_CACHE = new HashMap<>();

    private static final String REQUEST_MAPPING_HANDLER_MAPPING = "requestMappingHandlerMapping";

    /**
     * URI 路径分隔符
     */
    private static final String PATH_SEP = "/";
    /**
     * spring 上下文
     */
    private final ApplicationContext context;
    /**
     * 领域 ID 占位符
     */
    private static final String DOMAIN_KEY = "{domainId}";
    /**
     * 主键占位符
     */
    private static final String PRIMARY_KEY = "{id}";

    public RayquazaEndpoint(ApplicationContext context) {
        this.context = context;
    }

    /**
     * 注入所有的 Hook
     *
     * @param context 上下文
     */
    public static void injectAllHooks(ApplicationContext context) {
        //noinspection rawtypes
        Map<String, GenericHooks> genericHooksMap = context.getBeansOfType(GenericHooks.class);
        genericHooksMap.keySet().forEach(key -> {
            //noinspection rawtypes
            GenericHooks hooks = genericHooksMap.get(key);
            Type[] interfaces = hooks.getClass().getGenericInterfaces();
            Type[] arguments = ((ParameterizedType) interfaces[0]).getActualTypeArguments();
            // 实体类型
            Class<?> clazz = (Class<?>) arguments[0];
            Class<?> keyClass = (Class<?>) arguments[1];
            // 初始化 hook 空间
            HOOKS_MAP.putIfAbsent(clazz, new LinkedHashSet<>());
            Collection<GenericHooks<?, ?>> collection = HOOKS_MAP.get(clazz);
            if (Object.class.equals(clazz)) {
                if (Object.class.equals(keyClass)) {
                    // 添加到对应的空间
                    collection.add(hooks);
                }
            } else {
                // 添加到对应的空间
                collection.add(hooks);
            }
        });
    }


    public void onApplicationEvent(ApplicationContext context) {
        log.info("Rayquaza: endpoint start running");

        // 取出所有 Hook
        Map<String, EndpointHooks> hooks = context.getBeansOfType(EndpointHooks.class);

        injectAllHooks(context);

        // 执行入口点创建之前的 Hook
        hooks.forEach((key, it) -> {
            log.debug("Rayquaza: invoke {}beforeCreate hook -> {}", it.async() ? "async " : Strings.EMPTY, key);
            invoker.invoke(it, context, EndpointHookInvoker.HookType.BEFORE_CREATE);
        });

        // 获取请求映射处理器
        final RequestMappingHandlerMapping requestMappingHandlerMapping =
                context.getBean(REQUEST_MAPPING_HANDLER_MAPPING, RequestMappingHandlerMapping.class);

        // 执行入口点创建之前的 Hook
        hooks.forEach((key, it) -> {
            log.debug("Rayquaza: invoke {}beforeExport hook -> {}", it.async() ? "async " : Strings.EMPTY, key);
            invoker.invoke(it, context, EndpointHookInvoker.HookType.BEFORE_EXPORT);
        });

        // 导出 API
        this.exports(requestMappingHandlerMapping, context);
    }

    /**
     * 导出 API
     *
     * @param requestMappingHandlerMapping 获取请求映射处理器
     * @param context                      Spring Context 上下文
     */
    @SneakyThrows
    private <T, S extends IService<T>> void exports(RequestMappingHandlerMapping requestMappingHandlerMapping, ApplicationContext context) {
        // 导出的目标对象
        Map<String, Object> exportTargets = context.getBeansWithAnnotation(Export.class);

        // 遍历所有待导出的对象
        for (Object value : exportTargets.values()) {

            final Object target = ProxyUtils.getTarget(value);

            // 非真实类时，跳过处理
            if (target instanceof Proxy) {
                continue;
            }

            Class<?> clazz = target.getClass();

            // 当前类为 Mybatis Plus Service 类型时
            if (IService.class.isAssignableFrom(clazz)) {
                // 获取范型
                final Type superclass = clazz.getGenericSuperclass();
                final Type[] actualTypeArguments = ((ParameterizedType) superclass).getActualTypeArguments();

                // Mybatis Plus IService 泛型参数为两个
                if (actualTypeArguments.length != 2) {
                    continue;
                }

                // 获得实体类 class
                //noinspection unchecked
                Class<T> entityClass = (Class<T>) Class.forName(actualTypeArguments[1].getTypeName());

                Class<S> serviceClass = getServiceInterface(clazz);

                // 缓存 service 后续一对多接口导出时可以提高效率
                SERVICE_CACHE.putIfAbsent(entityClass.getTypeName(), (IService<?>) target);

                if (null != serviceClass && IService.class.isAssignableFrom(serviceClass)) {
                    // 执行接口注入
                    this.injectGenericController(entityClass, serviceClass, context, requestMappingHandlerMapping);
                }

            }

        }

    }

    @SneakyThrows
    @Nullable
    private static <T, S extends IService<T>> Class<S> getServiceInterface(Class<?> clazz) {
        // Spring 容器中注册的是 Service的实现类，Kernel 处理时需要的是接口，故需要获得父类定义
        final AnnotatedType[] annotatedInterfaces = clazz.getAnnotatedInterfaces();

        // 服务类的 Class
        Class<S> serviceClass = null;

        for (AnnotatedType annotatedInterface : annotatedInterfaces) {
            final Type type = annotatedInterface.getType();
            final Class<?> argClass = Class.forName(type.getTypeName());
            if (IService.class.isAssignableFrom(argClass)) {
                if (null != argClass.getDeclaredAnnotation(Export.class)) {
                    //noinspection unchecked
                    serviceClass = (Class<S>) argClass;
                    break;
                }
            }

        }
        return serviceClass;
    }

    /**
     * 注入通用控制器
     *
     * @param entityClass                  实体类型
     * @param serviceClass                 服务类型
     * @param context                      Spring 上下文
     * @param requestMappingHandlerMapping 请求映射
     */
    private <T, S extends IService<T>, P extends Serializable> void injectGenericController
    (Class<T> entityClass, Class<S> serviceClass, ApplicationContext context, RequestMappingHandlerMapping requestMappingHandlerMapping) {
        // ID 提供器
        //noinspection unchecked
        PrimaryKeyProvider<P> provider = context.getBean(PrimaryKeyProvider.class);
        // 获取配置项
        RayquazaProperties properties = context.getBean(RayquazaProperties.class);

        // 获取 export 注解
        Export export = serviceClass.getDeclaredAnnotation(Export.class);
        // 获取所有的关联注解
        Relation[] relations = this.getAllRelations(serviceClass);

        Collection<GenericHooks<?, ?>> genericHooks = HOOKS_MAP.get(entityClass);

        // 做一次中间转换，来确保类型正确
        Collection<GenericHooks<T, ?>> hooks = new ConcurrentLinkedQueue<>();

        // 全局 Hook，即泛型为 Object
        Collection<GenericHooks<?, ?>> globals = HOOKS_MAP.get(Object.class);

        if (null != globals) {
            for (GenericHooks<?, ?> global : globals) {
                //noinspection unchecked
                hooks.add((GenericHooks<T, ?>) global);
            }
        }

        if (null != genericHooks) {
            for (GenericHooks<?, ?> genericHook : genericHooks) {
                //noinspection unchecked
                hooks.add((GenericHooks<T, ?>) genericHook);
            }
        }

        // 根据 order 字段进行排序后重新转换为 list
        hooks = hooks.stream()
                .filter(Objects::nonNull)
                .sorted(Comparator.comparingInt(GenericHooks::order))
                .collect(Collectors.toList());

        // 生成控制器实例
        GenericController<T, S, P> controller = new GenericController<>();
        // 注入实体类型
        controller.entityClass(entityClass);
        // 注入服务类型
        controller.serviceClass(serviceClass);
        // 注入 Spring 上下文
        controller.context(context);
        // 注入 ID 提供器
        controller.provider(provider);
        // 设置当前接口的 Hooks
        controller.hooks(hooks);
        // 注册实体类接口路由
        this.registerEntityRoutes(export, controller, properties, requestMappingHandlerMapping);
        // 动态注册一对多接口
        this.injectEntityRelationsRoutes(export, relations, requestMappingHandlerMapping, entityClass);
    }

    private <S extends IService<?>> Relation[] getAllRelations(Class<S> serviceClass) {
        Set<Relation> relations = new HashSet<>();

        // 单个注解
        Relation relation = serviceClass.getDeclaredAnnotation(Relation.class);

        if (null != relation) {
            relations.add(relation);
        }

        Relations multiRelations = serviceClass.getDeclaredAnnotation(Relations.class);

        if (null != multiRelations) {
            Relation[] value = multiRelations.value();
            relations.addAll(Arrays.asList(value));
        }

        return relations.toArray(new Relation[0]);
    }

    /**
     * 动态注入关联表的接口
     *
     * @param export    导出注解，用于获取基准路径
     * @param relations 关联注解注入参数都在这边
     * @param mapping   用于注册接口
     */
    private void injectEntityRelationsRoutes(Export export, Relation[] relations, RequestMappingHandlerMapping mapping, Class<?> entityClass
    ) {
        for (Relation it : relations) {
            log.info("Route {} find relations -> {}", export.path(), it.value().getSimpleName());
            // 转换的目标实体类
            Class<?> targetClass = it.value();
            // 根据实体类型查找 service 实现类
            IService<?> service = findEntityService(targetClass);
            // 获取连接器
            RelationConnector connector = it.connector();
            if (null != service) {
                this.registerEntityRelationRoutes(it, export, entityClass, targetClass, connector.value(), mapping, RayquazaEndpoint.HOOKS_MAP);
            } else {
                log.warn("Relation Route inject failed for reason {} service not found", targetClass.getSimpleName());
            }
        }
    }

    @SneakyThrows
    private <E, RE, TE> void registerEntityRelationRoutes(Relation relations, Export export,
                                                              Class<E> entityClass, Class<TE> targetClass, Class<RE> relationClass,
                                                              RequestMappingHandlerMapping requestMappingHandlerMapping,
                                                              Map<Class<?>, Collection<GenericHooks<?, ?>>> hooks
    ) {

        // 构建 controller
        GenericRelationController<E, RE, TE, Serializable> controller = buildController(relations, entityClass, targetClass, relationClass);
        controller.hooks(hooks);

        // 构建接口前缀
        IService<?> targetService = findEntityService(targetClass);

        if (null != targetService) {
            Object tService = ProxyUtils.getTarget(targetService);
            String targetPath = getTargetPath(targetClass, getServiceInterface(tService.getClass()));

            for (Method method : controller.getClass().getDeclaredMethods()) {
                StringBuilder basePath = new StringBuilder().append(export.path());

                // 标注了以下请求注解的方法，将会被定义为接口
                final GetMapping get = method.getDeclaredAnnotation(GetMapping.class);
                final PostMapping post = method.getDeclaredAnnotation(PostMapping.class);
                final DeleteMapping delete = method.getDeclaredAnnotation(DeleteMapping.class);
                final PatchMapping patch = method.getDeclaredAnnotation(PatchMapping.class);

                // 根据注解获得构建好的 RequestMappingInfo 对象
                RequestMappingInfo mappingInfo = getRelationMappingInfo
                        (relations, basePath, targetPath, get, post, delete, patch);

                if (null != mappingInfo) {
                    String name = entityClass.getSimpleName();
                    // 构建主体实体名称
                    String key = controller.provider().key();
                    String domainId = name.substring(0, 1)
                            .toLowerCase().concat(name.substring(1))
                            .concat(key.substring(0, 1).toUpperCase().concat(key.substring(1)));
                    PatternsRequestCondition condition = mappingInfo.getPatternsCondition();

                    if (null != condition) {
                        String api = String.join(Strings.EMPTY,
                                        condition.getPatterns())
                                .replace(DOMAIN_KEY, String.format("{%s}", domainId))
                                .replace(PRIMARY_KEY, String.format("{%s}", key));
                        try {
                            requestMappingHandlerMapping.registerMapping(mappingInfo, controller, method);
                            log.info("\033[35mRelation Route [{}] exported with method -> {}",
                                    api, mappingInfo.getMethodsCondition());
                        } catch (IllegalStateException e) {
                            // fallback
                            log.warn("Route [{}] -> {} fallback, Reason: uri has been defined",
                                    api, mappingInfo.getMethodsCondition());
                        }
                    }
                }
            }
        }
    }

    private <E, RE, TE, P extends Serializable> GenericRelationController<E, RE, TE, P>
    buildController(Relation relations, Class<E> entityClass, Class<TE> targetClass, Class<RE> relationClass) {

        GenericRelationController<E, RE, TE, P> controller = new GenericRelationController<>();

        // ID 提供器
        //noinspection unchecked
        PrimaryKeyProvider<P> provider = context.getBean(PrimaryKeyProvider.class);

        controller.entityClass(entityClass);
        controller.relationEntityClass(relationClass);
        controller.targetEntityClass(targetClass);

        IService<?> service = findEntityService(entityClass);
        //noinspection unchecked
        controller.entityService((IService<E>) service);

        IService<?> targetService = findEntityService(targetClass);
        //noinspection unchecked
        controller.targetService((IService<TE>) targetService);

        IService<?> relationService = findEntityService(relationClass);
        //noinspection unchecked
        controller.relationService((IService<RE>) relationService);

        // 主键提供器
        controller.provider(provider)
                .context(context)
                .relations(relations);

        return controller;
    }

    /**
     * 获取目标类型的请求路径
     *
     * @param targetClass 目标实体类型
     * @param service     目标实体服务
     * @return 目标类型的请求路径
     */
    private String getTargetPath(Class<?> targetClass, Class<IService<Object>> service) {
        if (null != service) {
            Export export = service.getDeclaredAnnotation(Export.class);
            if (null != export) {
                return export.path().replaceAll("^/", Strings.EMPTY);
            }
        }
        return targetClass.getSimpleName().toLowerCase();
    }

    @SneakyThrows
    private IService<?> findEntityService(Class<?> targetClass) {
        String targetClassTypeName = targetClass.getTypeName();

        // 优先获取缓存
        IService<?> cache = SERVICE_CACHE.get(targetClassTypeName);
        log.debug("Service for {} cache matched", targetClassTypeName);

        if (null != cache) {
            return cache;
        }

        //noinspection rawtypes
        Map<String, IService> serviceMap = context.getBeansOfType(IService.class);
        //noinspection rawtypes
        for (IService service : serviceMap.values()) {
            Object target = ProxyUtils.getTarget(service);
            // 非真实类时，跳过处理
            if (target instanceof Proxy) {
                continue;
            }

            Type[] arguments = TypeUtil.getTypeArguments(service.getClass());
            if (null != arguments && arguments.length == 2) {
                // 实体类型
                Type type = arguments[1];
                // 缓存便于下个接口注册算法复杂度降低
                SERVICE_CACHE.putIfAbsent(type.getTypeName(), service);
                if (targetClassTypeName.equals(type.getTypeName())) {
                    return (IService<?>) target;
                }
            }
        }

        return null;
    }

    private <S extends IService<T>, T, P extends Serializable> void registerEntityRoutes
            (Export export, GenericController<T, S, P> controller, RayquazaProperties properties, RequestMappingHandlerMapping requestMappingHandlerMapping) {

        // 获取用户定义的接口基础路径
        String path = PATH_SEP.concat(export.path()
                .replaceAll("^/", Strings.EMPTY)
                .replaceAll("/$", Strings.EMPTY));

        // 遍历所有的方法
        for (Method method : controller.getClass().getDeclaredMethods()) {

            String name = method.getName();

            // Excel 导出接口需要额外判断
            if (GenericController.BINARY_METHOD_NAME.equals(name)) {
                // 必须存在实体类
                Class<T> entityClass = controller.entityClass();
                if (null == entityClass) {
                    continue;
                }
                // 必须有 Intermediary 注解加持
                Intermediary intermediary = entityClass.getDeclaredAnnotation(Intermediary.class);
                if (null == intermediary) {
                    continue;
                }
            }

            // 标注了以下请求注解的方法，将会被定义为接口
            final GetMapping get = method.getDeclaredAnnotation(GetMapping.class);
            final PostMapping post = method.getDeclaredAnnotation(PostMapping.class);
            final DeleteMapping delete = method.getDeclaredAnnotation(DeleteMapping.class);
            final PatchMapping patch = method.getDeclaredAnnotation(PatchMapping.class);

            // 构建接口前缀
            StringBuilder basePath = new StringBuilder();

            // 前缀根路径
            if (Strings.isNotEmpty(properties.getRoot())) {
                basePath.append(PATH_SEP)
                        .append(properties.getRoot()
                                .replaceAll("/$", Strings.EMPTY)
                                .replaceAll("^/", Strings.EMPTY));
            }

            // 拼接版本号
            if (Strings.isNotEmpty(properties.getVersion())) {
                basePath.append(PATH_SEP)
                        .append(properties.getVersion()
                                .replaceAll("/$", Strings.EMPTY)
                                .replaceAll("^/", Strings.EMPTY));
            }

            // 拼接用户定义路径
            basePath.append(path);

            // 根据注解获得构建好的 RequestMappingInfo 对象
            RequestMappingInfo mappingInfo = getMappingInfo(export, basePath, get, post, delete, patch);

            if (null != mappingInfo) {
                PatternsRequestCondition condition = mappingInfo.getPatternsCondition();
                if (null != condition) {
                    String key = controller.provider().key();

                    String api = String.join(Strings.EMPTY, condition.getPatterns())
                            .replace(PRIMARY_KEY, String.format("{%s}", key));

                    try {
                        requestMappingHandlerMapping.registerMapping(mappingInfo, controller, method);
                        log.info("\033[32mRoute [{}] exported with method -> {}",
                                api, mappingInfo.getMethodsCondition());
                    } catch (IllegalStateException e) {
                        // fallback
                        log.warn("\033[33mRoute [{}] -> {} fallback, Reason: uri has been defined",
                                api, mappingInfo.getMethodsCondition()
                        );
                    }

                }
            }


        }

    }

    /**
     * 构建请求映射详情
     *
     * @param export   {@link Export} 用户导出接口时的配置
     * @param basePath 用户
     * @param get      Get 请求映射注解
     * @param post     Post 请求映射注解
     * @param delete   Delete 请求映射注解
     * @param patch    Patch 请求映射注解
     * @return 请求映射详情
     */
    private RequestMappingInfo getMappingInfo(Export export, StringBuilder basePath,
                                              GetMapping get, PostMapping post, DeleteMapping delete, PatchMapping patch) {

        // 根据当前的方法类型，拼接出接口的路径
        String path = resolveSubPath(basePath, get, post, patch, delete);

        if (null == path) {
            return null;
        }

        RequestMappingInfo.Builder builder = RequestMappingInfo
                .paths(path);

        final boolean isGet = null != get && (export.page() || export.single());

        // 处理 Get 请求
        if (isGet) {
            // 单独判断 single 接口
            boolean isSingle = basePath.toString().endsWith("/{id}");

            if (isSingle && !export.single()) {
                return null;
            }

            return builder
                    .methods(RequestMethod.GET)
                    .build();
        }

        boolean isDelete = null != delete && export.delete();

        // 处理删除请求
        if (isDelete) {
            return builder
                    .methods(RequestMethod.DELETE)
                    .build();
        }


        boolean isUpdate = null != patch && export.update();

        // 处理更新请求
        if (isUpdate) {
            return builder
                    .methods(RequestMethod.PATCH)
                    .build();
        }

        boolean isPost = null != post && export.create();

        // 处理更新请求
        if (isPost) {
            return builder
                    .methods(RequestMethod.POST)
                    .build();
        }

        return null;
    }

    /**
     * 根据方法上的注解拼接出完整的接口路径用于构建请求映射
     *
     * @param basePath 用户配置的基础路径 {@link Export#path()}
     * @param get      Get 请求映射注解
     * @param post     Post 请求映射注解
     * @param delete   Delete 请求映射注解
     * @param patch    Patch 请求映射注解
     * @return 完整的接口路径
     */
    private String resolveSubPath(StringBuilder basePath, GetMapping get, PostMapping post, PatchMapping patch, DeleteMapping delete) {

        String[] value;

        if (null != get) {
            value = get.value();
        } else if (null != post) {
            value = post.value();
        } else if (null != patch) {
            value = patch.value();
        } else if (null != delete) {
            value = delete.value();
        } else {
            return null;
        }

        // 控制器定义的路径
        return basePath
                .append(Strings.join(Arrays.asList(value), PATH_SEP.toCharArray()[0])).toString();
    }

    @Override
    public void run(ApplicationArguments args) {
        onApplicationEvent(this.context);
    }
}
